package com.dkasza.qicky;

import java.io.*;
import java.net.*;
import java.util.ArrayList;

/** Quicky HttpServer.
 *  This class provides a standalone HTTP server for Quicky web applications.
 *  This HttpServer does not implement all features of the HTTP/1.1 standard;
 *  however, it implements just enough of it to be fast and reliable for simple
 *  web services.
 */
public class HttpServer extends Thread {
    /** Worker thread for Quicky HttpServer.
     *  This class handles a single connection to a client.
     */
    private class HttpServerThread extends Thread{
        /** Constructor for the class.
         *  @param c the socket to the client
         */
        public HttpServerThread(Socket c) {
            clientSocket = c;
        }
        
        /** The socket passed to the class when it is created.
         *  It is already connected to a client when the thread starts.
         */
        public Socket clientSocket;
        
        
        /** Main method of the HttpServerThread.
         * It is called automatically when the thread starts.
         */
        @Override
        public void run() {
            try (// these two object will be used to communicate with the client
                PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), false);
                BufferedReader in = new BufferedReader(
                        new InputStreamReader(clientSocket.getInputStream())) 
                ){
                boolean keepAlive = true;
                while(keepAlive && !clientSocket.isClosed()) {
                    // time to read the request
                    Http.Request request = new Http.Request(clientSocket);
                    request.messages = messages;
                    int contentLength = 0;
                    String shortRequest = in.readLine(); // just the first line
                    request.fullRequest = shortRequest; // the full multi line request
                    String[] splitShortRequest = shortRequest.split(" ");
                    request.method = splitShortRequest[0].toLowerCase();
                    request.path = splitShortRequest[1];
                    // DIRTY HACK TO HANDLE POST REQUESTS
                    if(request.method.equals("post")) {
                        request.method = "POST";
                    }
                    
                    String nextLine;
                    while(!(nextLine = in.readLine()).equals("")) { // did we reach the end of the request?
                        if(nextLine.toLowerCase().startsWith("content-length: ")) {
                            // get the content length for POST
                            contentLength = java.lang.Integer.valueOf(nextLine.substring(16));
                        }
                        // check if the POST method is compatible
                        if(nextLine.toLowerCase().startsWith("content-type: application/x-www-form-urlencoded") && request.method.equals("POST")) {
                            request.method = "post"; // yes, it is!
                        }
                        request.fullRequest = request.fullRequest + '\n' + nextLine; // store full request
                    }
                    keepAlive = request.fullRequest.toLowerCase().contains("\nconnection: keep-alive\n");
                    
                    // chek the method. Only GET, HEAD, and urlencoded POST supported
                    if(request.method.matches("get|head|post")) {  
                        // handle POST
                        char[] postData = new char[contentLength];
                        if(request.method.equals("post")) {
                            in.read(postData);
                            request.postData = parseUrlEncoded(new String(postData)); 
                            keepAlive = false; // assume posts are not keep-alive safe
                        }
                        // get GET parameters
                        request.getData = parseUrlEncoded(request.path);
                        // find the right request handler
                        boolean foundIt = false;
                        for(int i = 0; i < requestHandlers.size(); i++) {
                            HttpRequestHandler current = requestHandlers.get(i);
                            if(request.path.startsWith(current.prefix())) { // found it
                                foundIt = true;
                                Http.Response response = 
                                        current.handler(request);
                                keepAlive = keepAlive && response.keepAlive;
                                out.print(response.responseString);
                                break;
                            }
                        }
                        // if we couldn't find the requestHandler, 404 error
                        if(!foundIt) {
                            out.println(messages.headerNotFound+'\n'+messages.msgNotFound);
                            keepAlive = false;
                        }
                    } else {
                        // not supported method
                        out.println(messages.headerNotImplemented+'\n'+messages.msgNotImplemented);
                        keepAlive = false;
                    }

                    out.flush();
                }
                clientSocket.close();
            } catch(Exception e) {
                /* Sure, we have caught the exception, but the protocol does not
                 * really provide a way to handle it, so we just hope for the best.
                 */
            }
        }
    }
    
    /** This object store the response strings for this HttpServer object.
     */
    public HttpMessages messages = new HttpMessages();
    
    /** Main constructor for the Quicky HttpServer class.
     *
     * @param p port number
     */
    public HttpServer(int p) throws IOException {
        sSocket = new ServerSocket(p);
    }
    
    /** This list contains the list of the request handlers available to 
     *  this HttpServer.
     */
    private ArrayList<HttpRequestHandler> requestHandlers = new ArrayList<>();
    
    /** The serversocket used to accept connections from clients.
     */
    private ServerSocket sSocket;
    
    /** Main method of the HttpServer.
     *  This method is called when the HttpServer is started using the start()
     *  method, or the server can be started directly by calling this method;
     *  however, then the server won't have it's own thread.
     */
    @Override
    public void run() {
        try {
            while(!isInterrupted()) {
                (new HttpServerThread(sSocket.accept())).start(); 
                /* accept a new connection from a client and create a new thread
                 * to handle the communication with it.
                 */
            }
        } catch(IOException e) {
            /* TODO: Implement some exception handling here
             * Right now we just assume that this will always work.
             */
        }
    }
    
    /** Adds a new request handler to this HttpServer.
     *  @param rh the request handler to add
     */
    public void addRequestHandler(HttpRequestHandler rh) {
        requestHandlers.add(rh);
    }
    
    
    /*
     * Staic stuff starts here
     */
    
    
    public static class ClientData {
        public ClientData(String k, String val) {
            key = k;
            stringValue = val;
            try {
            numValue = java.lang.Long.valueOf(k);
            } catch(Exception e) {
                // this is not number, let's just set the numValue to zero
                numValue = 0;
            }
        }
        public String key;
        public String stringValue;
        public long   numValue;
    }
    
    public static ClientData[] parseUrlEncoded(String url) {
        if(url.contains("?")) { // the URL comes from a get
            url = url.split("\\?")[1]; // split it and take the encoded part only
        }
        ClientData[] retval;
        try {
            String[] parts;
            if(url.contains("&")) {
                parts = url.split("&"); // split it to key-value pairs
            } else {
                parts = new String[1];
                parts[0] = url;
            }
            retval = new ClientData[parts.length];
            for(int i = 0; i < parts.length; i++) { // for each pair
                String pair[] = parts[i].split("="); // separate
                pair[0] = java.net.URLDecoder.decode(pair[0]); // decode
                pair[1] = java.net.URLDecoder.decode(pair[1]);
                retval[i] = new ClientData(pair[0], pair[1]); // store
            }
        } catch(Exception e) { // something is wrong with this url
            retval = new ClientData[0];
        }
        return retval;
    }
}
